## Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions
## are met:
##  * Redistributions of source code must retain the above copyright
##    notice, this list of conditions and the following disclaimer.
##  * Redistributions in binary form must reproduce the above copyright
##    notice, this list of conditions and the following disclaimer in the
##    documentation and/or other materials provided with the distribution.
##  * Neither the name of NVIDIA CORPORATION nor the names of its
##    contributors may be used to endorse or promote products derived
##    from this software without specific prior written permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
## EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
## PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
## EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
## PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
## PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
## OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
##
## Copyright (c) 2008-2024 NVIDIA Corporation. All rights reserved.

import argparse
import os
import stat
import sys
import re
import platform
import shutil
from lib import utils
from lib import compare

# test mode: create copy of reference files
# update mode: try to open file in p4 if necessary
def setup_targetdir(metaDataDir, isTestMode):
	if isTestMode:
		targetDir = metaDataDir + "_test"
		if os.path.isdir(targetDir):
			print("deleting", targetDir)
			shutil.rmtree(targetDir)
		
		def ignore_non_autogen(dir, files):
			return [f for f in files if not (os.path.isdir(os.path.join(dir, f)) 
											or re.search(r"AutoGenerated", f))]
		
		shutil.copytree(metaDataDir, targetDir, ignore=ignore_non_autogen)
		#set write to new files:
		for root, dirs, files in os.walk(targetDir):
			for file in files:
				os.chmod(os.path.join(root, file) , stat.S_IWRITE|stat.S_IREAD)
	else:
		targetDir = metaDataDir
		
		if not utils.check_files_writable(utils.list_autogen_files(targetDir)):
			utils.try_checkout_files(utils.list_autogen_files(targetDir))
		
		if not utils.check_files_writable(utils.list_autogen_files(targetDir)):
			print("auto generated meta data files not writable:", targetDir)
			print("aborting")
			sys.exit(1)
			
	utils.clear_files(utils.list_autogen_files(targetDir))
	return targetDir
		
# test mode: run perl script to compare reference and generated files
def test_targetdir(targetDir, metaDataDir, isTestMode):

	if isTestMode:
		print("compare generated meta data files with reference meta data files:")
		result = compare.compareMetaDataDirectories(targetDir, metaDataDir)
		if not result:
			print("failed!")
			sys.exit(1)
		else:
			print("passed.")

def get_osx_platform_path():
	cmd = "xcodebuild -showsdks"
	(stdout, stderr) = utils.run_cmd(cmd)
	if stderr != "":
		print(stderr)
		sys.exit(1)

	match = re.search(r"(-sdk macosx\d+.\d+)", stdout,  flags=re.MULTILINE)
	if not match:
		print("coundn't parse output of:\n", cmd, "\naborting!")
		sys.exit(1)

	sdkparam = match.group(0)
	cmd = "xcodebuild -version " + sdkparam + " Path"
	(sdkPath, stderr) = utils.run_cmd(cmd)
	if stderr != "":
		print(stderr)
		sys.exit(1)
	print("using sdk path:", sdkPath.rstrip())
	return sdkPath.rstrip()
	
def includeString(path):
	return ' -I"' + path + '"'

###########################################################################################################
# main
###########################################################################################################

parser = argparse.ArgumentParser(description='Generates meta data source files.')
parser.add_argument('-test',	help='enables testing mode, internal only', action='store_true')

args = parser.parse_args()

scriptDir = os.path.dirname(os.path.realpath(__file__))

try:
	os.makedirs("temp")
except:
	None

# find SDK_ROOT and PX_SHARED
sdkRoot = utils.find_root_path(scriptDir, "source")

clangRoot = os.path.normpath(os.environ['PM_clangMetadata_PATH'])

print("testmode:", args.test)
print("root sdk:", sdkRoot)
print("root clang:", clangRoot)

boilerPlateFile = os.path.join(sdkRoot, os.path.normpath("tools/physxmetadatagenerator/PxBoilerPlate.h"))

includes = ''
includes += includeString(sdkRoot + '/include')
includes += includeString(sdkRoot + '/tools/physxmetadatagenerator')

print("platform:", platform.system())

commonFlags = '-DNDEBUG -DPX_GENERATE_META_DATA -DPX_ENABLE_FEATURES_UNDER_CONSTRUCTION=0 -x c++-header -w -Wno-c++11-narrowing -fms-extensions '


if platform.system() == "Windows":
	debugFile = open("temp/clangCommandLine_windows.txt", "a")
	
	# read INCLUDE variable, set by calling batch script
	sysIncludes = os.environ['INCLUDE']
	sysIncludes = sysIncludes.rstrip(';')
	sysIncludeList = sysIncludes.split(';')	
	sysIncludeFlags = ' -isystem"' + '" -isystem"'.join(sysIncludeList) + '"'
	
	# for some reason -cc1 needs to go first in commonFlags
	commonFlags = '-cc1 ' + commonFlags
	platformFlags = '-DPX_VC=14 -D_WIN32 -std=c++14' + sysIncludeFlags
	clangExe = os.path.join(clangRoot, os.path.normpath('win32/bin/clang.exe'))
	
	
elif platform.system() == "Linux":
	debugFile = open("temp/clangCommandLine_linux.txt", "a")
	
	platformFlags = '-std=c++0x'
	clangExe = os.path.join(clangRoot, os.path.normpath('linux32/bin/clang'))
	
elif platform.system() == "Darwin":
	debugFile = open("temp/clangCommandLine_osx.txt", "a")

	platformFlags = '-std=c++0x -isysroot' + get_osx_platform_path()
	clangExe = os.path.join(clangRoot, os.path.normpath('osx/bin/clang'))
else:
	print("unsupported platform, aborting!")
	sys.exit(1)
	
commonFlags += ' -boilerplate-file ' + boilerPlateFile
	
#some checks
if not os.path.isfile(clangExe):
	print("didn't find,", clangExe, ", aborting!")
	sys.exit(1)

clangExe = '"' + clangExe + '"'
	
# required for execution of clang.exe
os.environ["PWD"] = os.path.join(sdkRoot, os.path.normpath("tools/physxmetadatagenerator"))

###############################
# PxPhysicsWithExtensions     #
###############################

print("PxPhysicsWithExtensions:")

srcPath = "PxPhysicsWithExtensionsAPI.h"
metaDataDir = os.path.join(sdkRoot, os.path.normpath("source/physxmetadata"))

targetDir = setup_targetdir(metaDataDir, args.test)

cmd = " ".join(["", clangExe, commonFlags, "", platformFlags, includes, srcPath, "-o", '"'+targetDir+'"'])
print(cmd, file=debugFile)
(stdout, stderr) = utils.run_cmd(cmd)
if (stderr != "" or stdout != ""):
	print(stderr, "\n", stdout)
if (stderr != ""):
	print("failed to compile metadata!")
	sys.exit(1)
print("wrote meta data files in", targetDir)

test_targetdir(targetDir, metaDataDir, args.test)

###############################
# PxVehicleExtension          #
###############################

print("PxVehicleExtension:")

srcPath = "PxVehicleExtensionAPI.h"
metaDataDir = os.path.join(sdkRoot, os.path.normpath("source/physxvehicle/src/physxmetadata"))

includes += includeString(sdkRoot + '/include/vehicle')
#TODO, get rid of source include
includes += includeString(sdkRoot + '/source/physxvehicle/src')

targetDir = setup_targetdir(metaDataDir, args.test)

cmd = " ".join(["", clangExe, commonFlags, "", platformFlags, includes, srcPath, "-o", '"'+targetDir+'"'])
print(cmd, file=debugFile)
(stdout, stderr) = utils.run_cmd(cmd)
if (stderr != "" or stdout != ""):
	print(stderr, "\n", stdout)
if (stderr != ""):
	print("failed to compile metadata!")
	sys.exit(1)
print("wrote meta data files in", targetDir)

test_targetdir(targetDir, metaDataDir, args.test)

